#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/unit_test.h>

#include <boost/endian/conversion.hpp>

#include <complex>
#include <type_traits>

namespace ripple {
namespace test {

// a non-hashing Hasher that just copies the bytes.
// Used to test hash_append in base_uint
template <std::size_t Bits>
struct nonhash
{
    static constexpr auto const endian = boost::endian::order::big;
    static constexpr std::size_t WIDTH = Bits / 8;

    std::array<std::uint8_t, WIDTH> data_;

    nonhash() = default;

    void
    operator()(void const* key, std::size_t len) noexcept
    {
        assert(len == WIDTH);
        memcpy(data_.data(), key, len);
    }

    explicit
    operator std::size_t() noexcept
    {
        return WIDTH;
    }
};

struct base_uint_test : beast::unit_test::suite
{
    using test96 = base_uint<96>;
    static_assert(std::is_copy_constructible<test96>::value);
    static_assert(std::is_copy_assignable<test96>::value);

    void
    testComparisons()
    {
        {
            static constexpr std::
                array<std::pair<std::string_view, std::string_view>, 6>
                    test_args{
                        {{"0000000000000000", "0000000000000001"},
                         {"0000000000000000", "ffffffffffffffff"},
                         {"1234567812345678", "2345678923456789"},
                         {"8000000000000000", "8000000000000001"},
                         {"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"},
                         {"fffffffffffffffe", "ffffffffffffffff"}}};

            for (auto const& arg : test_args)
            {
                ripple::base_uint<64> const u{arg.first}, v{arg.second};
                BEAST_EXPECT(u < v);
                BEAST_EXPECT(u <= v);
                BEAST_EXPECT(u != v);
                BEAST_EXPECT(!(u == v));
                BEAST_EXPECT(!(u > v));
                BEAST_EXPECT(!(u >= v));
                BEAST_EXPECT(!(v < u));
                BEAST_EXPECT(!(v <= u));
                BEAST_EXPECT(v != u);
                BEAST_EXPECT(!(v == u));
                BEAST_EXPECT(v > u);
                BEAST_EXPECT(v >= u);
                BEAST_EXPECT(u == u);
                BEAST_EXPECT(v == v);
            }
        }

        {
            static constexpr std::array<
                std::pair<std::string_view, std::string_view>,
                6>
                test_args{{
                    {"000000000000000000000000", "000000000000000000000001"},
                    {"000000000000000000000000", "ffffffffffffffffffffffff"},
                    {"0123456789ab0123456789ab", "123456789abc123456789abc"},
                    {"555555555555555555555555", "55555555555a555555555555"},
                    {"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"},
                    {"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"},
                }};

            for (auto const& arg : test_args)
            {
                ripple::base_uint<96> const u{arg.first}, v{arg.second};
                BEAST_EXPECT(u < v);
                BEAST_EXPECT(u <= v);
                BEAST_EXPECT(u != v);
                BEAST_EXPECT(!(u == v));
                BEAST_EXPECT(!(u > v));
                BEAST_EXPECT(!(u >= v));
                BEAST_EXPECT(!(v < u));
                BEAST_EXPECT(!(v <= u));
                BEAST_EXPECT(v != u);
                BEAST_EXPECT(!(v == u));
                BEAST_EXPECT(v > u);
                BEAST_EXPECT(v >= u);
                BEAST_EXPECT(u == u);
                BEAST_EXPECT(v == v);
            }
        }
    }

    void
    run() override
    {
        testcase("base_uint: general purpose tests");

        static_assert(
            !std::is_constructible<test96, std::complex<double>>::value);
        static_assert(
            !std::is_assignable<test96&, std::complex<double>>::value);

        testComparisons();

        // used to verify set insertion (hashing required)
        std::unordered_set<test96, hardened_hash<>> uset;

        Blob raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
        BEAST_EXPECT(test96::bytes == raw.size());

        test96 u{raw};
        uset.insert(u);
        BEAST_EXPECT(raw.size() == u.size());
        BEAST_EXPECT(to_string(u) == "0102030405060708090A0B0C");
        BEAST_EXPECT(to_short_string(u) == "01020304...");
        BEAST_EXPECT(*u.data() == 1);
        BEAST_EXPECT(u.signum() == 1);
        BEAST_EXPECT(!!u);
        BEAST_EXPECT(!u.isZero());
        BEAST_EXPECT(u.isNonZero());
        unsigned char t = 0;
        for (auto& d : u)
        {
            BEAST_EXPECT(d == ++t);
        }

        // Test hash_append by "hashing" with a no-op hasher (h)
        // and then extracting the bytes that were written during hashing
        // back into another base_uint (w) for comparison with the original
        nonhash<96> h;
        hash_append(h, u);
        test96 w{std::vector<std::uint8_t>(h.data_.begin(), h.data_.end())};
        BEAST_EXPECT(w == u);

        test96 v{~u};
        uset.insert(v);
        BEAST_EXPECT(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3");
        BEAST_EXPECT(to_short_string(v) == "FEFDFCFB...");
        BEAST_EXPECT(*v.data() == 0xfe);
        BEAST_EXPECT(v.signum() == 1);
        BEAST_EXPECT(!!v);
        BEAST_EXPECT(!v.isZero());
        BEAST_EXPECT(v.isNonZero());
        t = 0xff;
        for (auto& d : v)
        {
            BEAST_EXPECT(d == --t);
        }

        BEAST_EXPECT(u < v);
        BEAST_EXPECT(v > u);

        v = u;
        BEAST_EXPECT(v == u);

        test96 z{beast::zero};
        uset.insert(z);
        BEAST_EXPECT(to_string(z) == "000000000000000000000000");
        BEAST_EXPECT(to_short_string(z) == "00000000...");
        BEAST_EXPECT(*z.data() == 0);
        BEAST_EXPECT(*z.begin() == 0);
        BEAST_EXPECT(*std::prev(z.end(), 1) == 0);
        BEAST_EXPECT(z.signum() == 0);
        BEAST_EXPECT(!z);
        BEAST_EXPECT(z.isZero());
        BEAST_EXPECT(!z.isNonZero());
        for (auto& d : z)
        {
            BEAST_EXPECT(d == 0);
        }

        test96 n{z};
        n++;
        BEAST_EXPECT(n == test96(1));
        n--;
        BEAST_EXPECT(n == beast::zero);
        BEAST_EXPECT(n == z);
        n--;
        BEAST_EXPECT(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF");
        BEAST_EXPECT(to_short_string(n) == "FFFFFFFF...");
        n = beast::zero;
        BEAST_EXPECT(n == z);

        test96 zp1{z};
        zp1++;
        test96 zm1{z};
        zm1--;
        test96 x{zm1 ^ zp1};
        uset.insert(x);
        BEAST_EXPECTS(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE", to_string(x));
        BEAST_EXPECTS(to_short_string(x) == "FFFFFFFF...", to_short_string(x));

        BEAST_EXPECT(uset.size() == 4);

        test96 tmp;
        BEAST_EXPECT(tmp.parseHex(to_string(u)));
        BEAST_EXPECT(tmp == u);
        tmp = z;

        // fails with extra char
        BEAST_EXPECT(!tmp.parseHex("A" + to_string(u)));
        tmp = z;

        // fails with extra char at end
        BEAST_EXPECT(!tmp.parseHex(to_string(u) + "A"));

        // fails with a non-hex character at some point in the string:
        tmp = z;

        for (std::size_t i = 0; i != 24; ++i)
        {
            std::string x = to_string(z);
            x[i] = ('G' + (i % 10));
            BEAST_EXPECT(!tmp.parseHex(x));
        }

        // Walking 1s:
        for (std::size_t i = 0; i != 24; ++i)
        {
            std::string s1 = "000000000000000000000000";
            s1[i] = '1';

            BEAST_EXPECT(tmp.parseHex(s1));
            BEAST_EXPECT(to_string(tmp) == s1);
        }

        // Walking 0s:
        for (std::size_t i = 0; i != 24; ++i)
        {
            std::string s1 = "111111111111111111111111";
            s1[i] = '0';

            BEAST_EXPECT(tmp.parseHex(s1));
            BEAST_EXPECT(to_string(tmp) == s1);
        }

        // Constexpr constructors
        {
            static_assert(test96{}.signum() == 0);
            static_assert(test96("0").signum() == 0);
            static_assert(test96("000000000000000000000000").signum() == 0);
            static_assert(test96("000000000000000000000001").signum() == 1);
            static_assert(test96("800000000000000000000000").signum() == 1);

// Everything within the #if should fail during compilation.
#if 0
            // Too few characters
            static_assert(test96("00000000000000000000000").signum() == 0);

            // Too many characters
            static_assert(test96("0000000000000000000000000").signum() == 0);

            // Non-hex characters
            static_assert(test96("00000000000000000000000 ").signum() == 1);
            static_assert(test96("00000000000000000000000/").signum() == 1);
            static_assert(test96("00000000000000000000000:").signum() == 1);
            static_assert(test96("00000000000000000000000@").signum() == 1);
            static_assert(test96("00000000000000000000000G").signum() == 1);
            static_assert(test96("00000000000000000000000`").signum() == 1);
            static_assert(test96("00000000000000000000000g").signum() == 1);
            static_assert(test96("00000000000000000000000~").signum() == 1);
#endif  // 0

            // Using the constexpr constructor in a non-constexpr context
            // with an error in the parsing throws an exception.
            {
                // Invalid length for string.
                bool caught = false;
                try
                {
                    // Try to prevent constant evaluation.
                    std::vector<char> str(23, '7');
                    std::string_view sView(str.data(), str.size());
                    [[maybe_unused]] test96 t96(sView);
                }
                catch (std::invalid_argument const& e)
                {
                    BEAST_EXPECT(
                        e.what() ==
                        std::string("invalid length for hex string"));
                    caught = true;
                }
                BEAST_EXPECT(caught);
            }
            {
                // Invalid character in string.
                bool caught = false;
                try
                {
                    // Try to prevent constant evaluation.
                    std::vector<char> str(23, '7');
                    str.push_back('G');
                    std::string_view sView(str.data(), str.size());
                    [[maybe_unused]] test96 t96(sView);
                }
                catch (std::range_error const& e)
                {
                    BEAST_EXPECT(
                        e.what() == std::string("invalid hex character"));
                    caught = true;
                }
                BEAST_EXPECT(caught);
            }

            // Verify that constexpr base_uints interpret a string the same
            // way parseHex() does.
            struct StrBaseUint
            {
                char const* const str;
                test96 tst;

                constexpr StrBaseUint(char const* s) : str(s), tst(s)
                {
                }
            };
            constexpr StrBaseUint testCases[] = {
                "000000000000000000000000",
                "000000000000000000000001",
                "fedcba9876543210ABCDEF91",
                "19FEDCBA0123456789abcdef",
                "800000000000000000000000",
                "fFfFfFfFfFfFfFfFfFfFfFfF"};

            for (StrBaseUint const& t : testCases)
            {
                test96 t96;
                BEAST_EXPECT(t96.parseHex(t.str));
                BEAST_EXPECT(t96 == t.tst);
            }
        }
    }
};

BEAST_DEFINE_TESTSUITE(base_uint, basics, ripple);

}  // namespace test
}  // namespace ripple
